package org.visionlibrary.image.geomtric.util;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Random;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.visionlibrary.image.model.Histogram;


/**
 * Klasa narzedziowa zawierajaca metody nie zwiazane bezposrednio z innymi klasami.
 */
public class Utils {
	private static long ID = Calendar.getInstance().getTimeInMillis();

	public static long generateId() { // not thread safe
		return ID++;
	}

	private static final Random random = new Random(System.nanoTime());

	public static int generateColorValue() {
	    Integer c;
	    do c = Integer.valueOf(random.nextInt(16777214) + 1);
	    while (c.intValue() == 0);
	    return c.intValue();
	}
	
	public static double norm(double oMin, double oMax, double nMin, double nMax, double value) {
		double ratio = (double) (nMax - nMin) / (double) (oMax - oMin);
		double result = value;
		result -= oMin;
		result *= ratio;
		result += nMin;
		return result;
	}
	
	public static String prettyXmlFormat(String input, int indent) {
		try {
			Source xmlInput = new StreamSource(new StringReader(input));
			StringWriter stringWriter = new StringWriter();
			StreamResult xmlOutput = new StreamResult(stringWriter);
			Transformer transformer = TransformerFactory.newInstance()
					.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", String
							.valueOf(indent));
			transformer.transform(xmlInput, xmlOutput);
			return xmlOutput.getWriter().toString();
		} catch (Exception e) {
			throw new RuntimeException(e); // simple exception handling, please
			// review it
		}
	}

	public static String prettyXmlFormat(String input) {
		return prettyXmlFormat(input, 2);
	}

	public static int intPercentage(int value, int percentage) {
		return (int) doublePercentage(value, percentage);
	}

	public static double doublePercentage(double value, double percentage) {
		return (value * percentage) / 100d;
	}

	public static double roundDouble(double val, int places) {
		long factor = (long) Math.pow(10, places);
		val = val * factor;
		long tmp = Math.round(val);
		return (double) tmp / factor;
	}

	public static <E> List<E> copyFromRange(List<E> list, int beginIndex,
			int endIndex) {
		if (null == list)
			throw new NullPointerException("List is null.");

		if (beginIndex < 0 || beginIndex > list.size())
			throw new IndexOutOfBoundsException("BeginIndex is out of range.");

		if (endIndex < 0 || endIndex > list.size())
			throw new IndexOutOfBoundsException("EndIndex is out of range.");

		if (beginIndex > endIndex)
			throw new IllegalArgumentException(
					"BeginIndex is greater then EndIndex");

		List<E> copy = new ArrayList<E>();
		for (int i = beginIndex; i <= endIndex; i++)
			copy.add(list.get(i));

		return copy;
	}

	public static String getExtension(File f) {
		String ext = null;
		String s = f.getName();
		int i = s.lastIndexOf('.');

		if (i > 0 && i < s.length() - 1) {
			ext = s.substring(i + 1).toLowerCase();
		}
		return ext;
	}

	public static String extension(String fullPath, char extensionSeparator) {
		int dot = fullPath.lastIndexOf(extensionSeparator);
		return fullPath.substring(dot + 1);
	}

	public static String filenameWithExtension(String fullPath,
			char pathSeparator) {
		int sep = fullPath.lastIndexOf(pathSeparator);
		if (-1 == sep)
			sep = 0;
		return fullPath.substring(sep, fullPath.length());
	}

	public static String filename(String fullPath, char extensionSeparator,
			char pathSeparator) { // gets filename without extension
		int dot = fullPath.lastIndexOf(extensionSeparator);
		if (-1 == dot)
			dot = fullPath.length();
		int sep = fullPath.lastIndexOf(pathSeparator);
		if (-1 == sep)
			sep = 0;

		return fullPath.substring(sep + 1, dot);
	}

	public static String path(String fullPath, char pathSeparator) {
		int sep = fullPath.lastIndexOf(pathSeparator);
		if (-1 == sep)
			sep = fullPath.length();
		return fullPath.substring(0, sep);
	}

	public static void copyFile(File sourceFile, File destFile)
			throws IOException {
		if (!destFile.exists()) {
			destFile.createNewFile();
		}

		FileChannel source = null;
		FileChannel destination = null;
		try {
			source = new FileInputStream(sourceFile).getChannel();
			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(source, 0, source.size());
		} finally {
			if (source != null) {
				source.close();
			}
			if (destination != null) {
				destination.close();
			}
		}
	}

	public static BufferedImage printHistogram(Histogram histogram,
			String label, boolean printBinLabels, boolean randomBinColor) {

		int doublePrecision = 5; // ilosc miejsc po przecinku do jakich bedzie
		// zaokraglana wartosc

		// tworze obrazek by dostac sie do obiektu graphics
		// pozwoli mi to na pobranie danych dotyczacych wielkosci
		// czcionki
		BufferedImage dummyImage = new BufferedImage(2, 2,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D dummyGraphics = dummyImage.createGraphics();
		FontMetrics dummyMetrics = dummyGraphics.getFontMetrics();

		int binLabelWidth = dummyMetrics.stringWidth(""
				+ Utils.roundDouble(1.23456789d, doublePrecision));
		// wysokosc etykiety po obkreceniu
		int fontPixelHeight = dummyMetrics.getHeight();
		int binWidthGap = 2; // rozmiar naddatku dla dlugosci wyswietlanego binu
		int binWidth = 2 * binWidthGap + fontPixelHeight; // jako ze wyswietlamy
		// czcionke pionow to jako ze obrocona bedzie o 90 stopni jej wysokosc
		// wplywa na dlugosc binu
		if (!printBinLabels)
			binWidth = 1;// piksele

		int sampleHeight = 4; // minimalny rozmiar sampla histogramu na obrazku
		// czyli ile pikseli bedzie zajmowac pojedyncza jednostka wartosci binu
		int sideGapSize = 5; // wielkosc marginesow (ile miejsca pomiedzy
		// krawdziami obrazka)

		int numberOfHistograms = histogram.getNumBands(); // ile jest
		// histogramow, moze byc wiele histogramow w jednym obiekcie klasy
		// Histogram
		int numberOfBins = histogram.getNumBins(); // ilosc binow (koszykow)
		// histogramu

		double maximalSampleSize = 0; // maksymalna wielkosc przechowywana w
		// histogramie

		int upperGap = 5;
		int gapBeforeLabel = 5;

		Color defaultBgColor = Color.WHITE; // domyslny kolor tła
		Color defaultFgColor = Color.BLACK; // domyslny kolor kresek, etykiet
		// itp

		for (int i = 0; i < numberOfHistograms; i++) {
			for (int j = 0; j < numberOfBins; j++) {
				double maximalTemp = histogram.getBin(i, j);
				if (maximalSampleSize < maximalTemp)
					maximalSampleSize = maximalTemp;
			}
		}

		// dlugosc kreski pokazujacej histogram (ilosc binow * dlugosc
		// pojedynczego binu)
		int histogramWidth = numberOfBins * binWidth;
		int histogramHeight = 0;

		if (histogram.isNormalized()) {
			maximalSampleSize = 100;
		}

		histogramHeight = (int) maximalSampleSize * sampleHeight;

		int imageWidth = histogramWidth + 2 * sideGapSize;

		int blockSize = upperGap + histogramHeight + gapBeforeLabel
				+ binLabelWidth + gapBeforeLabel + fontPixelHeight;
		int imageHeight = numberOfHistograms * blockSize + gapBeforeLabel;

		BufferedImage image = new BufferedImage(imageWidth, imageHeight,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g2d = image.createGraphics();
		g2d.setBackground(defaultBgColor);
		g2d.clearRect(0, 0, image.getWidth(), image.getHeight());

		Font baseFont = g2d.getFont();
		AffineTransform fontRotateTransform = AffineTransform
				.getRotateInstance(Math.toRadians(-90));
		Font rotatedFont = baseFont.deriveFont(fontRotateTransform);

		Color constantBinColor = null;
		if (!randomBinColor)
			constantBinColor = Debug.Misc.getRandomColor();

		for (int i = 0; i < numberOfHistograms; i++) {
			// pionowa linia
			int verticalLineX1 = sideGapSize;
			int verticalLineX2 = sideGapSize;
			int verticalLineY1 = (i + 1) * upperGap + i * (blockSize);
			int verticalLineY2 = verticalLineY1 + histogramHeight;

			// pozioma linia
			int horizontalX1 = sideGapSize;
			int horizontalX2 = sideGapSize + histogramWidth;
			int horizontalY1 = verticalLineY1 + histogramHeight;
			int horizontalY2 = verticalLineY1 + histogramHeight;

			// poszczegolne biny histogramu
			for (int j = 0; j < numberOfBins; j++) {
				int rectX = sideGapSize + j * binWidth;
				int rectY = 0;

				if (histogram.isNormalized()) {
					rectY = verticalLineY2
							- (int) (maximalSampleSize * histogramHeight * Utils
									.roundDouble(histogram.getBin(i, j),
											doublePrecision));
				} else {
					rectY = verticalLineY2
							- ((int) histogram.getBin(i, j) * sampleHeight);
				}

				if (!randomBinColor)
					g2d.setColor(constantBinColor);
				else
					g2d.setColor(Debug.Misc.getRandomColor());
				g2d.fillRect(rectX, rectY, binWidth, verticalLineY2 - rectY);

				g2d.setColor(defaultFgColor);
				g2d.drawRect(rectX, rectY, binWidth, verticalLineY2 - rectY);

				if (printBinLabels) {
					g2d.setColor(defaultFgColor);
					double r = Utils.roundDouble(histogram.getBin(i, j),
							doublePrecision);
					g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
							RenderingHints.VALUE_ANTIALIAS_ON);
					g2d.setFont(rotatedFont);
					g2d.drawString("" + r, rectX + fontPixelHeight
							- binWidthGap, verticalLineY2 + gapBeforeLabel
							+ binLabelWidth);
					g2d.setFont(baseFont);
					g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
							RenderingHints.VALUE_ANTIALIAS_OFF);
				}
			}

			// linie rysowane teraz zeby byly bardziej na wierzchu
			g2d.setColor(defaultFgColor);
			g2d.drawLine(verticalLineX1, verticalLineY1, verticalLineX2,
					verticalLineY2);
			g2d
					.drawLine(horizontalX1, horizontalY1, horizontalX2,
							horizontalY2);

			if (!label.isEmpty())
				g2d.drawString(label, sideGapSize, verticalLineY2
						+ gapBeforeLabel + binLabelWidth + gapBeforeLabel
						+ fontPixelHeight);
		}

		return image;
	}

	public static void drawHistogram(Histogram histogram, String label,
			boolean printBinLabels, boolean randomBinColor, String fileName,
			String destinationDir) {
		Debug.Image.setDebugFolder(destinationDir);
		Debug.Image.info(printHistogram(histogram, label, printBinLabels,
				randomBinColor), fileName);
	}

	public static List<File> getXMLFilesFromDir(String directory) {
		return getXMLFilesFromDir(new File(directory));
	}
	
	public static List<File> getXMLFilesFromDir(File directory) {
		FileFilter filter = new FileFilter() {			
			@Override
			public boolean accept(File pathname) {
				return pathname.getName().endsWith("_merged.xml");
			}
		};
		return getFilesFromDir(directory, filter);
	}
	
	public static List<File> getFilesFromDir(File directory, FileFilter filter) {
		File[] files = directory.listFiles(filter);
		return Arrays.asList(files);
	}
	
	public static String getTimeSeedString() {
		long seed = (new GregorianCalendar()).getTimeInMillis();
		return "" + seed;
	}

	public static File createDir(String path, String dirName)
			throws IOException {
		return createDir(new File(path), dirName);
	}

	public static File createDir(File pathWhere, String dirName)
			throws IOException {
		if (null == pathWhere)
			throw new NullPointerException("Path is null.");

		if (!pathWhere.exists())
			throw new FileNotFoundException("Path doesnt exist.");

		if (!pathWhere.canWrite())
			throw new IOException("Path is not writable.");

		if (!pathWhere.isDirectory())
			throw new IOException("Path is not a directory.");

		File output = new File(pathWhere.getAbsoluteFile() + "/" + dirName
				+ "/");
		if (output.exists())
			throw new IOException("Directory already exists.");

		if (output.mkdir()) {
			return output;
		}

		return null;
	}
}
